//#######################################################################################################
//#################################### Plugin-095: NTP Client ###########################################
//#######################################################################################################

/*********************************************************************************************\
 * This protocol provides NPT time sync
 * 
 * Author             : Martinus van den Broek
 * Support            : www.nodo-domotica.nl
 * Date               : Jul 2014
 * Version            : BETA 0.1
 * Compatibility      : R744
 \*********************************************************************************************/

#define PLUGIN_ID 95
#define PLUGIN_NAME_095 "NTPSync"

void Plugin_095_saveTime(unsigned long time);

#include <Udp.h>

class EthernetUDP : public UDP {
private:
  uint8_t _sock;  // socket ID for Wiz5100
  uint16_t _port; // local port to listen on
  IPAddress _remoteIP; // remote IP address for the incoming packet whilst it's being processed
  uint16_t _remotePort; // remote port for the incoming packet whilst it's being processed
  uint16_t _offset; // offset into the packet being sent

public:
  EthernetUDP();  // Constructor
  virtual uint8_t begin(uint16_t);	// initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use
  virtual void stop();  // Finish with the UDP socket
  virtual int beginPacket(IPAddress ip, uint16_t port);
  virtual int beginPacket(const char *host, uint16_t port);
  virtual int endPacket();
  virtual size_t write(uint8_t);
  virtual size_t write(const uint8_t *buffer, size_t size);
  using Print::write;
  virtual int parsePacket();
  virtual int available();
  virtual int read();
  virtual int read(unsigned char* buffer, size_t len);
  virtual int read(char* buffer, size_t len) { return read((unsigned char*)buffer, len); };
  virtual int peek();
  virtual void flush();	// Finish reading the current packet
  virtual IPAddress remoteIP() { return _remoteIP; };
  virtual uint16_t remotePort() { return _remotePort; };
};

EthernetUDP Udp;

boolean Plugin_095(byte function, struct NodoEventStruct *event, char *string)
{
  boolean success=false;

  switch(function)
  {
#ifdef PLUGIN_095_CORE
  case PLUGIN_COMMAND:
    {
      if (!TerminalClient.connected()) // NTP connect only if telnet is not connected or we might run short on W5100 sockets!
        {
          const long timeZoneOffset = 7200L;   
          unsigned int localPort = 8888;
          const int NTP_PACKET_SIZE= 48;      
          byte packetBuffer[NTP_PACKET_SIZE];

          IPAddress address(193,79,237,14); // ntp1.nl.net server

          Udp.begin(localPort);

          memset(packetBuffer, 0, NTP_PACKET_SIZE); 
          packetBuffer[0] = 0b11100011;
          packetBuffer[1] = 0;
          packetBuffer[2] = 6;
          packetBuffer[3] = 0xEC;
          packetBuffer[12]  = 49; 
          packetBuffer[13]  = 0x4E;
          packetBuffer[14]  = 49;
          packetBuffer[15]  = 52;		   
          Udp.beginPacket(address, 123);
          Serial.println("Sending...");
          Udp.write(packetBuffer,NTP_PACKET_SIZE);
          Udp.endPacket(); 

          Serial.println("Receiving...");
          delay(1000);

          if (Udp.parsePacket())
            {
              Serial.println("Received...");
              Udp.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer
              unsigned long highWord, lowWord, epoch;
              highWord = word(packetBuffer[40], packetBuffer[41]);
              lowWord = word(packetBuffer[42], packetBuffer[43]);  
              epoch = highWord << 16 | lowWord;
              epoch = epoch - 2208988800 + timeZoneOffset;
              Plugin_095_saveTime(epoch);
            }
          Udp.stop();
        }
      success=true;
      break;
    } // case

#endif // PLUGIN_095_CORE
    #if NODO_MEGA // alleen relevant voor een Nodo Mega want de Small heeft geen MMI!
    case PLUGIN_MMI_IN:
      {
      char *TempStr=(char*)malloc(INPUT_COMMAND_SIZE);
      if(GetArgv(string,TempStr,1))
        {
        if(strcasecmp(TempStr,PLUGIN_NAME_095)==0)
          {
            event->Type = NODO_TYPE_PLUGIN_COMMAND;
            event->Command = PLUGIN_ID; // Plugin nummer  
            success=true;
          }
        }
      free(TempStr);
      break;
      }

    case PLUGIN_MMI_OUT:
      {
      strcpy(string,PLUGIN_NAME_095);
      break;
      }
    #endif //MMI
  }      
  return success;
}

#ifdef PLUGIN_095_CORE
#define LEAP_YEAR(Y)     ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
 
void Plugin_095_saveTime(unsigned long time)
{
  uint8_t year;
  uint8_t month, monthLength;
  unsigned long days;
  
  uint8_t Second; 
  uint8_t Minute; 
  uint8_t Hour; 
  uint8_t Wday;   // day of week, sunday is day 1
  uint8_t Day;
  uint8_t Month; 
  uint16_t Year;

  Second = time % 60;
  time /= 60; // now it is minutes
  Minute = time % 60;
  time /= 60; // now it is hours
  Hour = time % 24;
  time /= 24; // now it is days
  Wday = ((time + 4) % 7) + 1;  // Sunday is day 1 
  
  year = 0;  
  days = 0;
  while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
    year++;
  }
  Year = year + 1970; // year is offset from 1970 
  
  days -= LEAP_YEAR(year) ? 366 : 365;
  time  -= days; // now it is days in this year, starting at 0
  
  days=0;
  month=0;
  monthLength=0;
  for (month=0; month<12; month++) {
    if (month==1) { // february
      if (LEAP_YEAR(year)) {
        monthLength=29;
      } else {
        monthLength=28;
      }
    } else {
      monthLength = monthDays[month];
    }
    
    if (time >= monthLength) {
      time -= monthLength;
    } else {
        break;
    }
  }
  Month = month + 1;  // jan is month 1  
  Day = time + 1;     // day of month

  char datestr[24];
  sprintf(datestr, "%04d-%02d-%02d  %02d:%02d:%02d  ", Year, Month, Day, Hour, Minute, Second);
  Serial.println(datestr);

  rtc[DS1307_SEC]      =DS1307_CLOCKHALT;
  DS1307_save();
  
  rtc[DS1307_MIN]      = ((Minute/10)<<4)+(Minute%10);
  rtc[DS1307_HR]       = ((Hour/10)<<4)+(Hour%10);
  rtc[DS1307_DOW]      = Wday;
  rtc[DS1307_DATE]     = ((Day/10)<<4)+(Day%10);
  rtc[DS1307_MTH]      = ((Month/10)<<4)+(Month%10);
  rtc[DS1307_YR]       = (((Year-DS1307_BASE_YR)/10)<<4)+(Year%10); 
  rtc[DS1307_SEC]      = ((Second/10)<<4)+(Second%10);
  rtc[DS1307_DLS]      = Time.DaylightSaving;
  rtc[DS1307_DLS_M]    = Time.DaylightSavingSetMonth;
  rtc[DS1307_DLS_D]    = Time.DaylightSavingSetDate;
  rtc[DS1307_RESERVED] = 0;
  DS1307_save();
}
#endif
